package com.uinnova.product.eam.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.uinnova.product.eam.config.CacheKeyPrefix;
import com.uinnova.product.eam.service.CasLoginService;
import com.uinnova.product.eam.service.asset.BmConfigSvc;
import com.uinnova.project.base.diagram.comm.constant.CommonConst;
import com.uinnova.project.base.diagram.util.RedisUtil;
import com.uino.api.client.permission.IRoleApiSvc;
import com.uino.api.client.permission.IUserApiSvc;
import com.uino.bean.permission.base.SysRole;
import com.uino.bean.permission.base.SysUser;
import com.uino.bean.permission.business.UserInfo;
import com.uino.bean.sys.base.SysLoginLog;
import com.uino.dao.permission.ESUserSvc;
import com.uino.service.sys.microservice.ILogSvc;
import com.uino.util.sys.BeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.AssertionImpl;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.uinnova.product.eam.init.cassso.CASConfig.GUOTOU_USER_KEY_PREFIX;

/**
 * @description: 单点登录接口实现类
 * @author: LiMG
 * @create: 2024-05-28 11:31:58
 **/
@Slf4j
@Service
@ConditionalOnProperty(name = "monet.login.loginMethod", havingValue = "guotouiam")
public class CasLoginServiceImpl implements CasLoginService {

    @Autowired
    private BmConfigSvc bmConfigSvc;

    @Autowired
    private TicketValidator ticketValidator;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private IUserApiSvc userApiSvc;

    @Autowired
    private ILogSvc iLogSvc;

    @Autowired
    private ESUserSvc userSvc;

    @Autowired
    private IRoleApiSvc roleApiSvc;

    /**
     * 获取要跳转的国投登录页面地址
     *
     * @return
     */
    @Override
    public String getJumpLoginUrl() {
        JSONObject json = getCasLoginConfig();
        String jumpLoginUrl = json.getString("jumpLoginUrl");
        if (StrUtil.isEmpty(jumpLoginUrl)) {
            log.error("无法获取国投IAM单点登录配置项中的[jumpLoginUrl]配置项");
            return null;
        }
        String eaServerUrl = json.getString("eaServerUrl");
        Boolean urlEncode = json.getBoolean("urlEncode");
        if (urlEncode) {
            eaServerUrl = URLEncoder.encode(eaServerUrl);
        }
        jumpLoginUrl = jumpLoginUrl.replace("${service}", eaServerUrl);
        return jumpLoginUrl;
    }

    /**
     * 获取要跳转的国投登出页面地址
     *
     * @return
     */
    @Override
    public String getJumpLogoutUrl() {
        JSONObject json = getCasLoginConfig();
        String jumpLogoutUrl = json.getString("jumpLogoutUrl");
        if (StrUtil.isEmpty(jumpLogoutUrl)) {
            log.error("无法获取国投IAM单点登录配置项中的[jumpLogoutUrl]配置项");
            return null;
        }
        String eaServerUrl = json.getString("eaServerUrl");
        Boolean urlEncode = json.getBoolean("urlEncode");
        if (urlEncode) {
            eaServerUrl = URLEncoder.encode(eaServerUrl);
        }
        jumpLogoutUrl = jumpLogoutUrl.replace("${service}", eaServerUrl);
        return jumpLogoutUrl;
    }

    private Assertion mock() {
        String principal = "zhangsan";
        Map<String, Object> attributes = Maps.newHashMap();
        attributes.put("type", "staff");
        attributes.put("phone", "13999999999");
        attributes.put("email", "zhangsan@uino.com");
        return new AssertionImpl(new AttributePrincipalImpl(principal, attributes));
    }

    /**
     * 校验登录票据
     *
     * @param ticket
     * @param request
     * @param response
     */
    @Override
    public void checkLoginTicket(String ticket, String goPageUrl, HttpServletRequest request, HttpServletResponse response) throws IOException {
        Assert.isTrue(StrUtil.isNotEmpty(ticket), "登录票据不能为空");
        JSONObject config = getCasLoginConfig();
        String eaServerUrl = config.getString("eaServerUrl");
        try {
            // 请求IAM认证服务校验票据有效性
            final Assertion assertion = ticketValidator.validate(ticket, eaServerUrl);
            // 本地测试，执行mock
//            final Assertion assertion = mock();
//            boolean valid = assertion.isValid();
//            if (valid) {
                // 用户登录票据验证成功
                AttributePrincipal principal = assertion.getPrincipal();
                // 用户名
                String account = principal.getName();
                // 用户信息
                Map<String, Object> attributes = principal.getAttributes();
                Object user = this.getUser(ticket);
                if (null == user) {
                    // 缓存中没有用户数据，从EA数据库中查询
                    UserInfo userInfo;
                    try {
                        userInfo = userApiSvc.getUserInfoByLoginCode(account);
                        log.info("用户：{},登录成功", userInfo.getLoginCode());
                    } catch (Exception e) {
                        log.error("用户信息不存在EA数据库，执行用户同步任务");
                        userInfo = this.syncUserInfo(account, attributes, config);
                    }
                    // 设置用户信息缓存
                    this.setUser(userInfo, ticket);
                    // 添加登录日志
                    this.addLoginLog(userInfo);
                }
//            } else {
//                new RemoteResult(false);
//                log.error("IAM登录票据验证失败");
//                Cookie cookie = new Cookie("error", URLEncoder.encode("IAM登录票据验证失败"));
//                cookie.setPath("/");
//                response.addHeader("Access-Control-Allow-Credentials", "true");
//                response.addCookie(cookie);
//                response.sendRedirect(eaServerUrl + "login.html");
//                return;
//            }
            // 登录成功，同步用户信息
        } catch (Exception e) {
            log.error("IAM登录票据验证失败: {}", e.getMessage(), e);
//            response.sendRedirect(eaServerUrl + "login.html");
            response.sendRedirect(this.getJumpLoginUrl());
            return;
        }
        Cookie cookie = new Cookie("IAMToken", ticket);
        cookie.setPath("/");
        response.addHeader("Access-Control-Allow-Credentials", "true");
        response.addCookie(cookie);
        //處理前端傳過來的 null 字符串
        if(goPageUrl != null && !"null".equals(goPageUrl) && !goPageUrl.contains("ticket=")){
            response.sendRedirect(goPageUrl);
            return;
        }

        response.sendRedirect(eaServerUrl + "index.html?tk=" + ticket);
    }

    /**
     * 票据登出
     *
     * @param ticket
     * @param response
     * @throws IOException
     */
    @Override
    public void ticketLogout(String ticket, Integer type, HttpServletResponse response) throws IOException {
        JSONObject config = getCasLoginConfig();
        if (StrUtil.isNotEmpty(ticket)) {
            /** 删除redis中的token */
            String redisTicket = GUOTOU_USER_KEY_PREFIX + ticket;
            try {
                redisUtil.del(redisTicket);
                cleanOnlineUserCache(ticket);
            } catch (Exception e) {
                log.error("redis删除登录会话异常{}", JSON.toJSONString(e));
            }
        }
        String eaServerUrl = config.getString("eaServerUrl");
        if (null != type) {
            // 跳转到国投IAM登出页面
            String jumpLogoutUrl = config.getString("jumpLogoutUrl");
            Boolean urlEncode = config.getBoolean("urlEncode");
            jumpLogoutUrl = jumpLogoutUrl.replace("${service}", eaServerUrl);
            if (urlEncode) {
                jumpLogoutUrl = jumpLogoutUrl.replace("${service}", URLEncoder.encode(eaServerUrl));
            }
            response.sendRedirect(jumpLogoutUrl);
            return;
        }
        Cookie cookie = new Cookie("IAMToken", ticket);
        // 设置cookie过期
        cookie.setMaxAge(0);
        cookie.setPath("/");
        response.addHeader("Access-Control-Allow-Credentials", "true");
        response.addCookie(cookie);
        response.sendRedirect(this.getJumpLoginUrl());
//        response.sendRedirect(eaServerUrl + "login.html");
    }

    /**
     * 清除在线用户缓存
     * @param ticket
     */
    private void cleanOnlineUserCache(String ticket) {
        StringBuilder sb = new StringBuilder("cas:ticket").append(":").append(ticket);
        Object o = redisUtil.get(sb.toString());
        if (o == null) {
            return;
        }
        String loginCode = o.toString();
        redisUtil.del(sb.toString());
        redisUtil.del(CacheKeyPrefix.UINO_USER_ONLINE_PREFIX + ":" + loginCode);
    }



    /**
     * 从缓存中获取用户信息
     *
     * @param authorization
     * @return
     */
    @Override
    public Object getUser(String authorization) {
        Object userObj = null;
        try {
            String key = GUOTOU_USER_KEY_PREFIX + authorization;
            if (authorization.contains("Bearer ")) {
                key = GUOTOU_USER_KEY_PREFIX + authorization.substring("Bearer ".length());
            }
            userObj = redisUtil.get(key);

            /**
             * 重新设置过期时间
             */
            if (userObj != null) {
                redisUtil.expire(key, CommonConst.LOCAL_TOKEN_EXPIRE_TIME);
            }
        } catch (Exception e) {
            log.error("redis异常:{}", JSON.toJSONString(e));
        }
        return userObj;
    }

    /**
     * 从国投IAM中获取用户信息
     * @param ticket
     * @return
     */
    @Override
    public JSONObject getUserInfo(String ticket) {
        JSONObject jsonObject = new JSONObject();
        JSONObject config = getCasLoginConfig();
        String eaServerUrl = config.getString("eaServerUrl");
        try {
            final Assertion assertion = ticketValidator.validate(ticket, eaServerUrl);
            // 本地测试，执行mock
//            final Assertion assertion = mock();
            if (assertion.isValid()) {
                jsonObject.put("account", assertion.getPrincipal().getName());
                jsonObject.put("code", 0);
            } else {
                jsonObject.put("code", -1);
                log.error("登录票据验证失败");
            }
        } catch (Exception e) {
            log.error("登录票据验证失败: {}", e.getMessage(), e);
            jsonObject.put("code", -1);
            log.error("登录票据验证失败");
        }
        return jsonObject;
    }

    /**
     * 设置用户信息缓存
     *
     * @param userInfo
     * @param authorization
     */
    @Override
    public void setUser(UserInfo userInfo, String authorization) {
        try {
            String key = GUOTOU_USER_KEY_PREFIX + authorization;
            if (authorization.contains("Bearer ")) {
                key = GUOTOU_USER_KEY_PREFIX + authorization.substring("Bearer ".length());
            }
            redisUtil.set(key, userInfo, CommonConst.LOCAL_TOKEN_EXPIRE_TIME);
        } catch (Exception e) {
            log.error("redis异常{}", JSON.toJSONString(e));
        }
    }

    /**
     * 添加登录日志
     *
     * @param userInfo
     */
    @Async
    public void addLoginLog(UserInfo userInfo) {
        try {
            SysLoginLog loginLog = iLogSvc.addLoginLog(userInfo.getId());
            SysUser user = BeanUtil.converBean(userInfo, SysUser.class);
            user.setLastLoginLogId(loginLog.getId());
            user.setLastLoginTime(System.currentTimeMillis());
            // 更新用户登录信息
            userSvc.saveOrUpdate(user);
        } catch (Exception e) {
            log.error("写入登录日志异常：{}", JSON.toJSONString(e));
        }
        log.info("异步写入登录日志结束-{}", userInfo.getLoginCode());
    }

    /**
     * 同步用户信息
     *
     * @return
     */
    private UserInfo syncUserInfo(String account, Map<String, Object> attributes, JSONObject config) {
        UserInfo userInfo = new UserInfo();
        userInfo.setLoginCode(account);
        userInfo.setUserName(account);
        userInfo.setIcon("");
        userInfo.setCreator("WIKI_AUTO_CREATE");
        userInfo.setSuperUserFlag(0);
        userInfo.setStatus(1);
        userInfo.setLockFlag(0);
        userInfo.setTryTimes(0);
        JSONObject userField = config.getJSONObject("userFields");
        JSONObject jsonObject = config.getJSONObject("iamRoleToEa");
        if (null != jsonObject && null != userField) {
            // 设置邮箱地址
            userInfo.setEmailAdress(String.valueOf(attributes.get(userField.getString("email"))));
            // 设置手机号
            userInfo.setMobileNo(String.valueOf(attributes.get(userField.getString("phone"))));
            // 设置默认角色
            String roleName = jsonObject.getString(String.valueOf(attributes.get(userField.getString("type"))));
            Set<SysRole> roles = new HashSet<>();
            Set<String> role = new HashSet<>();
            role.add(roleName);
            BoolQueryBuilder must = QueryBuilders.boolQuery()
                    .must(QueryBuilders.termsQuery("roleName.keyword", role));
            List<SysRole> sysRoles = roleApiSvc.getRolesByQuery(must);
            roles.addAll(sysRoles);
            userInfo.setRoles(roles);
        }
        /** 创建用户 */
        userApiSvc.saveOrUpdate(userInfo);
        log.info("用户：{},登录成功", userInfo.getLoginCode());
        return userApiSvc.getUserInfoByLoginCode(userInfo.getLoginCode());
    }

    /**
     * 获取视图配置中的单点登录配置项
     *
     * @return
     */
    private JSONObject getCasLoginConfig() {
        String config = bmConfigSvc.getConfigType("IAM_CAS_LOGIN_CONFIG");
        if (StrUtil.isEmpty(config)) {
            log.error("无法获取视图配置中的国投IAM单点登录配置项");
            return null;
        }
        return JSON.parseObject(config);
    }
}
